﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;

namespace KamLib
{
    public class QueryArgs
    {
        public int StartIndex = -1;
        public int LimitCount = -1;
        public int TotalCount;
        /// <summary>
        /// Показывает, ожидается ли, что будет установлено значение Total
        /// </summary>
        public bool TotalExpected;

        public IDictionary<string, object> KeyValues;

        public DbFieldID.List Fields;
        public SortField.List SortFields = new SortField.List();
        public bool UseOrderAsThen;

        public Filter.List Filters;
        public bool UseFilters = true;

        public QueryArgs() { }
        public QueryArgs(IDictionary<string, object> key)
        {
            if (key != null)
                KeyValues = key;
        }


        #region Types

        public enum SortDirection { Default, ASC, DESC }
        public enum Comparison { Equal, GreatOrEqual, LessOrEqual }
        public enum FilterType { Boolean, Date, List, Numeric, String }

        public class SortField
        {
            public string Name;
            public SortDirection Dir;
            public bool Applied;

            public class List : List<SortField>
            {
                public void Add(string name, SortDirection dir = SortDirection.ASC)
                {
                    Add(new SortField { Name = name, Dir = dir });
                }

                public void Insert(int index, string name, SortDirection dir = SortDirection.ASC)
                {
                    Insert(index, new SortField { Name = name, Dir = dir });
                }

                public void ToSql(StringBuilder sb, QueryArgs e, params string[] names)
                {
                    var hasNames = names != null && names.Length != 0;

                    var items = this.Where(a => !a.Applied);
                    if (hasNames)
                        items = items.Where(a => names.FirstOrDefault(b => string.Equals(b, a.Name, StringComparison.InvariantCultureIgnoreCase)) != null);

                    var isNext = false;
                    foreach (var item in items)
                    {
                        if (isNext) sb.Append(", "); else isNext = true;
                        item.ToSql(sb, e);
                        if (hasNames) item.Applied = true;
                    }
                }

                public string ToSql(QueryArgs e)
                {
                    var sb = new StringBuilder();
                    ToSql(sb, e);
                    return sb.ToString();
                }
            }

            public void ToSql(StringBuilder sb, QueryArgs e)
            {
                sb.Append(e.FieldSqlName(Name));
                if (Dir == SortDirection.DESC)
                    sb.Append(" desc");
            }
        }

        public class Filter
        {
            public string Name;
            public object Value;
            public FilterType FilterType;
            public Comparison Comparison = Comparison.Equal;
            public bool Applied;

            public Filter(FilterType filterType, string name, object value, Comparison comparison = Comparison.Equal)
            {
                FilterType = filterType;
                Name = name;
                Value = value;
                Comparison = comparison;
            }
            public Filter(string name, object value, Comparison comparison = Comparison.Equal)
            {
                Name = name;
                Value = value;
                Comparison = comparison;
                if (value == null)
                    FilterType = FilterType.String;
                else
                {
                    var t = value.GetType();
                    FilterType =
                        t == typeof(bool) || t == typeof(bool?) ? FilterType.Boolean :
                        t == typeof(DateTime) || t == typeof(DateTime?) ? FilterType.Date :
                        t == typeof(IEnumerable) ? FilterType.List :
                        t == typeof(int) || t == typeof(int?) || t == typeof(Int64) || t == typeof(Int64) || t == typeof(double) || t == typeof(double?) ? FilterType.Numeric :
                        FilterType.String;
                }
            }


            public class List : List<Filter>
            {
                public List() { }
                public List(IEnumerable<Filter> collection) : base(collection) { }

                public void ToSql(StringBuilder sb, QueryArgs e, params string[] names)
                {
                    var hasNames = names != null && names.Length != 0;

                    var items = this.Where(a => !a.Applied);
                    if (hasNames)
                        items = items.Where(a => names.FirstOrDefault(b => string.Equals(b, a.Name, StringComparison.InvariantCultureIgnoreCase)) != null);

                    var isNext = false;
                    foreach (var item in items)
                    {
                        if (isNext) sb.Append(" and "); else isNext = true;
                        item.ToSql(sb, e);
                        if (hasNames) item.Applied = true;
                    }
                }
                public string ToSql(QueryArgs e)
                {
                    var sb = new StringBuilder();
                    ToSql(sb, e);
                    return sb.ToString();
                }
            }

            public void ToSql(StringBuilder sb, QueryArgs e)
            {
                sb.Append(e.FieldSqlName(Name));

                switch (Comparison)
                {
                    case Comparison.Equal:
                        switch (FilterType)
                        {
                            case FilterType.List:
                                sb.Append(" in (");
                                var isNext = false;
                                foreach (var lvalue in ((IEnumerable<string>)Value).Reverse())
                                {
                                    if (isNext) sb.Append(", "); else isNext = true;
                                    sb.Append(SystemDataHelper.ToParam(lvalue));
                                }
                                sb.Append(")");
                                break;
                            case FilterType.String:
                                var s = Value as string;
                                if (s != null && (s = s.Trim().ToLower()).yes())
                                {
                                    sb.Append(" like ");
                                    if (s.Contains("*") || s.Contains("%"))
                                        sb.Append(SystemDataHelper.ToParam(s.Replace('*', '%')));
                                    else
                                        sb.Append(SystemDataHelper.ToParam(s + "%"));
                                }
                                break;
                            default:
                                sb.Append(" like ");
                                sb.Append(SystemDataHelper.ToParam(Value));
                                break;
                        }
                        break;
                    case Comparison.GreatOrEqual:
                        sb.Append(" >= ");
                        sb.Append(SystemDataHelper.ToParam(Value));
                        break;
                    case Comparison.LessOrEqual:
                        sb.Append(" <= ");
                        sb.Append(SystemDataHelper.ToParam(Value));
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }

            }
        }

        #endregion


        #region Utils

        public int GetTotalCount(IDbConnection cn, string tableName, string where)
        {
            TotalCount = TotalExpected ? cn.ExecInt(String.Concat("select count(*) from ", tableName, " ", @where)) : -1;
            return TotalCount;
        }

        public string FieldSqlName(string name)
        {
            var field = Fields != null ? Fields[name] : null;
            return field != null ? field.SqlName() : name;
        }

        public void MakeAppliedFilters(params string[] fields)
        {
            if (Filters == null || Filters.Count == 0) return;
            foreach (var field in fields)
            {
                var f = Filters.FirstOrDefault(a => a.Name == field);
                if (f != null)
                    f.Applied = true;
            }
        }

        #endregion


        #region Sql

        private string _Where, _PartWhere;

        public string Where(params string[] names)
        {
            if (Filters == null) return null;

            var hasNames = names != null && names.Length != 0;
            if (!hasNames && _Where != null) return _Where;

            var sb = new StringBuilder();
            Filters.ToSql(sb, this, names);
            return sb.Length > 0 ? (_Where = "\r\n where " + sb) : null;
        }

        public string PartWhere(params string[] names)
        {
            if (Filters == null) return null;

            var hasNames = names != null && names.Length != 0;
            if (!hasNames && _PartWhere != null) return _PartWhere;

            var sb = new StringBuilder();
            Filters.ToSql(sb, this, names);
            return sb.Length > 0 ? (_PartWhere = " and " + sb) : null;
        }


        public string OrderBy(params string[] names)
        {
            if (SortFields == null) return null;
            var sb = new StringBuilder();
            SortFields.ToSql(sb, this, names);
            return sb.Length > 0 ? "\r\n order by " + sb : null;
        }

        public string PartOrderBy(params string[] names)
        {
            if (SortFields == null) return null;
            var sb = new StringBuilder();
            SortFields.ToSql(sb, this, names);
            return sb.Length > 0 ? ", " + sb : null;
        }


        public string Limit()
        {
            if (SystemDataHelper.IsMySql)
                return LimitCount <= 0 ? null : " \r\n limit " + (StartIndex > 0 ? StartIndex + ", " : null) + LimitCount;

            return null;
        }

        #endregion

    }


}
